/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.atlascon.java2gpb.serialization;

import com.google.protobuf.Message;
import com.google.protobuf.Message.Builder;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

/**
 *
 * @author Tom
 */
public class SerializationInspector {

    private final GPBSerializable annotation;
    private final Class<?> c;
    private static final Map<Class<?>, SerializationInspector> instances = new ConcurrentHashMap<>();
    private static final ReentrantLock lock = new ReentrantLock();
    private volatile Map<Field, GPBSerializable> fields;
    private volatile Map<Method, GPBSerializable> methods;
    private volatile Method postDeser = null;

    public static SerializationInspector get(Class<?> cls) {
        lock.lock();
        try {
            SerializationInspector get = instances.get(cls);
            if (get == null) {
                get = new SerializationInspector(cls);
                instances.put(cls, get);
            }
            return get;
        } finally {
            lock.unlock();
        }
    }
    
    public Class getFieldParamClass(Field f) throws ClassNotFoundException {
        Class c = fields.get(f).collectionMemberType();        
        return c;
    }

    private SerializationInspector(Class<?> cls) {
        annotation = cls.getAnnotation(GPBSerializable.class);
        c = cls;
        getDeclaredFields();
        getDeclaredMethods();
        for(Method m : methods.keySet()) {
            if(m.getAnnotation(GPBPostDeserialize.class)!=null) {
                postDeser = m;
                break;
            }
        }
    }

    public boolean hasAnnotation() {
        return annotation != null;
    }

    public GPBSerializable getAnnotation() {
        return annotation;
    }

    public Builder newBuilder() throws Exception {
        if (annotation == null) {
            throw new IllegalArgumentException("Not GPBSerializable! class "+c);
        }
        Class aClass = annotation.className();
        Message.Builder builder = (Message.Builder) aClass.getMethod("newBuilder").invoke(null);
        return builder;
    }

    public Map<Field, GPBSerializable> getDeclaredFields() {
        if (fields != null) {
            return fields;
        } else {
            Field[] fs = c.getDeclaredFields();
            Map<Field, GPBSerializable> map = new ConcurrentHashMap<>();
            for (Field f : fs) {
                GPBSerializable a = f.getAnnotation(GPBSerializable.class);
                if (a != null) {
                    map.put(f, a);
                    f.setAccessible(true);
                }
            }
            fields = map;
            return fields;
        }
    }

    public Map<Method, GPBSerializable> getDeclaredMethods() {
        if (methods != null) {
            return methods;
        } else {
            Method[] ms = c.getDeclaredMethods();
            Map<Method, GPBSerializable> map = new ConcurrentHashMap<>();
            for (Method m : ms) {
                GPBSerializable a = m.getAnnotation(GPBSerializable.class);
                if (a != null) {
                    map.put(m, a);
                    m.setAccessible(true);
                }
            }
            methods = map;
            return methods;
        }
    }

    public String getFieldName(Field f) {
        GPBSerializable ser = getDeclaredFields().get(f);
        if(ser!=null && !ser.name().trim().isEmpty()) {
            return ser.name();
        } else {
            return f.getName();
        }
    }
       
    public boolean hasPostDeserializeMethod() {
        return postDeser != null;
    }

    public Method getPostDeserializeMethod() {
        return postDeser;
    }
}
